package route

import (
	"archive/zip"
	"bytes"
	"encoding/json"
	"fmt"
	"gitee.com/gitee-go/core"
	"gitee.com/gitee-go/core/bean/hbtpBean"
	"gitee.com/gitee-go/core/common"
	"gitee.com/gitee-go/core/model/pipeline"
	"gitee.com/gitee-go/core/models"
	"gitee.com/gitee-go/dag-core/dag"
	"gitee.com/gitee-go/server/comm"
	ymldag "gitee.com/gitee-go/server/comm/yml/dag"
	"gitee.com/gitee-go/server/engine"
	comm2 "gitee.com/gitee-go/server/engine/comm"
	"gitee.com/gitee-go/server/service"
	"gitee.com/gitee-go/utils"
	"gitee.com/gitee-go/utils/httpex"
	"gitee.com/gitee-go/utils/ioex"
	"github.com/gin-gonic/gin"
	"gopkg.in/yaml.v2"
	"io"
	"io/ioutil"
	"net/url"
	"os"
	"path/filepath"
	"strings"
	"time"
)

type PipelineController struct{}

func (PipelineController) GetPath() string {
	return "/api/pipeline"
}
func (c *PipelineController) Routes(g gin.IRoutes) {
	g.Use(service.MidUserCheck)
	g.POST("/versionDetails", utils.GinReqParseJson(c.versionDetails))
	g.POST("/versionYml", utils.GinReqParseJson(c.versionYml))
	g.POST("/save", utils.GinReqParseJson(c.savePipeline))
	g.POST("/deleted", utils.GinReqParseJson(c.deletedVersion))
	g.POST("/jobGroup", utils.GinReqParseJson(c.jobGroup))
	g.POST("/jobLog", utils.GinReqParseJson(c.jobLog))
	g.GET("/downLog/:id", c.downLog)
	g.GET("/downJobLog/:id", c.downJobLog)
	g.POST("/stop", utils.GinReqParseJson(c.stop))
	g.POST("/plugins", utils.GinReqParseJson(c.plugins))
	g.POST("/artifacts", utils.GinReqParseJson(c.artifacts))
	g.POST("/artifact/delete", utils.GinReqParseJson(c.artDel))
	g.GET("/artifact/downs", c.artDowns)
}

/**
获取构建详情
1. 连表查找构建详情(pipeline_version:构建信息,build:构建运行时)
2. 循环填充前端需要的字段
*/
func (PipelineController) versionDetails(c *gin.Context, m *utils.Map) {
	pipelineVersionId := m.GetString("pipelineVersionId")
	if pipelineVersionId == "" {
		core.NewUIRes(common.UICodeErrParam).
			ResJson(c)
		return
	}
	db := comm.DBMain.GetDB()
	//查询数据库
	sqls := "select pv.*,bd.* ,pv.status as tpv_status , bd.status as build_status, pv.error as tpv_error FROM `t_pipeline_version` pv " +
		"left join `t_build` bd on bd.pipeline_version_id=pv.id" +
		" where pv.id=? and pv.deleted != 1 "
	spv := &models.ShowPipelineVersionBuild{}
	get, err := db.SQL(sqls, pipelineVersionId).Get(spv)
	if err != nil {
		core.NewUIRes(common.UICodeErrs).
			SetMsgObjs(err).
			ResJson(c)
		return
	}
	if !get {
		core.NewUIResOk(spv).ResJson(c)
		return
	}
	repo := &pipeline.TRepo{}
	get, err = comm.DBMain.GetDB().Where("id = ?", spv.RepoId).Get(repo)
	if err != nil {
		core.NewUIRes(common.UICodeErrs).
			SetMsgObjs(err).
			ResJson(c)
		return
	}
	spv.CompletionFieldValue(repo)
	stages := make([]*models.TStage, 0)
	err = db.Where("pipeline_version_id = ?", spv.Id).Asc("sort").Find(&stages)
	if err != nil {
		core.NewUIRes(common.UICodeErrs).
			SetMsgObjs(err).
			ResJson(c)
		return
	}
	spv.Stages = stages
	// 填充前端需要的信息
	for _, stage := range stages {
		jobs := make([]*models.TJob, 0)
		err = db.Where("stage_id = ?", stage.Id).Asc("sort").Find(&jobs)
		if err != nil {
			core.NewUIRes(common.UICodeErrs).
				SetMsgObjs(err).
				ResJson(c)
			return
		}

		for _, job := range jobs {
			job.ExpendTime = int64(job.Finished.Sub(job.Started).Seconds())
			if job.Finished.IsZero() && !job.Started.IsZero() {
				job.ExpendTime = int64(time.Since(job.Started).Seconds())
			} else if !job.Finished.IsZero() && job.Started.IsZero() {
				job.ExpendTime = 0
			}
		}

		stage.ExpendTime = int64(stage.Finished.Sub(stage.Started).Seconds())
		if stage.Finished.IsZero() && !stage.Started.IsZero() {
			stage.ExpendTime = int64(time.Since(stage.Started).Seconds())
		}
		stage.Jobs = jobs
	}
	core.NewUIResOk(spv).ResJson(c)
}
func (PipelineController) versionYml(c *gin.Context, m *utils.Map) {
	pipelineVersionId := m.GetString("pipelineVersionId")
	if pipelineVersionId == "" {
		core.NewUIRes(common.UICodeErrParam).
			ResJson(c)
		return
	}
	vs := &pipeline.TPipelineVersion{}
	get, err := comm.DBMain.GetDB().Where("id = ?", pipelineVersionId).Get(vs)
	if err != nil {
		core.NewUIRes(common.UICodeErrs).
			SetMsgObjs(err).
			ResJson(c)
		return
	}
	if !get {
		core.NewUIRes(common.UICodeErrNotFound).
			ResJson(c)
		return
	}
	core.NewUIResOk(vs.YmlContent).ResJson(c)
}

// deprecated 弃用
func (PipelineController) jobGroup(c *gin.Context, m *utils.Map) {
	jobId := m.GetString("jobId")
	if jobId == "" {
		core.NewUIRes(common.UICodeErrParam).
			ResJson(c)
		return
	}
	db := comm.DBMain.GetDB()
	ls := make([]*models.TCmdGroup, 0)
	err := db.Where("job_id=?", jobId).
		OrderBy("num ASC,created ASC").Find(&ls)
	if err != nil {
		core.NewUIRes(common.UICodeErrs).
			SetMsgObjs(err).
			ResJson(c)
		return
	}
	ids := make([]string, 0)
	grps := make(map[string]*models.TCmdGroup)
	for _, v := range ls { // 查询数据库填充前端需要的字段
		ids = append(ids, v.Id)
		if v.Started.IsZero() {
			v.ExpendTime = 0
		} else if v.Finished.IsZero() {
			v.ExpendTime = int64(time.Since(v.Started).Seconds())
		} else {
			v.ExpendTime = int64(v.Finished.Sub(v.Started).Seconds())
		}
		grps[v.Id] = v
		db.Where("job_id=? and group_id=?", jobId, v.Id).
			OrderBy("num ASC,created ASC").Find(&v.Commands)
		for _, cmd := range v.Commands {
			if cmd.Started.IsZero() {
				cmd.ExpendTime = 0
			} else if cmd.Finished.IsZero() {
				cmd.ExpendTime = int64(time.Since(cmd.Started).Seconds())
			} else {
				cmd.ExpendTime = int64(cmd.Finished.Sub(cmd.Started).Seconds())
			}
		}
	}
	core.NewUIResOk(utils.Map{
		"groupIds": ids,
		"groups":   grps,
	}).ResJson(c)
}

/**
返回前端构建日志
1. 判断文件,按offset跳转到文件位置
2. 循环文件流,每一行解析为json,添加到slice返回前端

日志文件储存格式为(一行一个json,包括内容,时间,cmdid,groupid):
{"id":"60f68006cf1e8c000100003f","gid":"60f68005cf1e8c000100003c","pid":"60f68005cf1e8c000100003d","type":"sysrun","content":"","times":"2021-07-20T07:49:26.05703888Z","code":0,"offset":0}
{"id":"60f68006cf1e8c0001000040","gid":"60f68005cf1e8c000100003c","pid":"60f68005cf1e8c000100003d","type":"cmderr","content":"bash: ${{P}}: bad substitution","times":"2021-07-20T07:49:26.058307133Z","code":0,"offset":0}
{"id":"60f68006cf1e8c0001000041","gid":"60f68005cf1e8c000100003c","pid":"60f68005cf1e8c000100003d","type":"cmdend","content":"","times":"2021-07-20T07:49:26.073450662Z","code":1,"offset":0}
{"id":"60f68006cf1e8c0001000042","gid":"","pid":"","type":"sysend","content":"","times":"2021-07-20T07:49:26.162425898Z","code":0,"offset":0}
*/
func (PipelineController) jobLog(c *gin.Context, m *utils.Map) {
	jobId := m.GetString("jobId")
	offset, _ := m.GetInt("offset")
	limit, _ := m.GetInt("limit")
	if jobId == "" {
		core.NewUIRes(common.UICodeErrParam).
			ResJson(c)
		return
	}
	job := &models.TJob{}
	ok, _ := comm.DBMain.GetDB().Where("id=?", jobId).Get(job)
	if !ok {
		core.NewUIRes(common.UICodeErrNotFound).
			ResJson(c)
		return
	}
	dir := filepath.Join(comm.WorkPath, comm.Build, job.BuildId, comm.Jobs)
	logpth := filepath.Join(dir, fmt.Sprintf("%v.log", job.Id))
	fl, err := os.Open(logpth)
	if err != nil {
		core.NewUIRes(common.UICodeErrNotFound).
			ResJson(c)
		return
	}
	defer fl.Close()
	off := offset
	if offset > 0 {
		off, err = fl.Seek(offset, 0)
		if err != nil {
			core.NewUIRes(common.UICodeErrs).
				SetMsgObjs(err).
				ResJson(c)
			return
		}
	}
	ls := make([]*hbtpBean.CmdLogLineJson, 0)
	bts := make([]byte, 1024*5)
	linebuf := &bytes.Buffer{}
	for !ioex.CheckContext(c) { // 循环解析日志文件,每一行一个json
		rn, err := fl.Read(bts)
		if rn > 0 {
			for i := 0; i < rn; i++ {
				off++
				b := bts[i]
				if linebuf == nil && b == '{' {
					linebuf.Reset()
				}
				if linebuf != nil {
					if b == '\n' {
						e := &hbtpBean.CmdLogLineJson{}
						err := json.Unmarshal(linebuf.Bytes(), e)
						linebuf.Reset()
						if err == nil {
							/*if e.Type == hbtpBean.TypeCmdLogLineSys {
								continue
							}*/
							if e.Type == hbtpBean.TypeCmdLogLineSysrun {
								continue
							}
							e.Offset = off - 1
							ls = append(ls, e)
						}
						if limit > 0 && limit >= int64(len(ls)) {
							break
						}
					} else {
						linebuf.WriteByte(b)
					}
				}
			}
		}
		if err != nil {
			break
		}
	}
	core.NewUIResOk(ls).ResJson(c)
}
func (PipelineController) downLog(c *gin.Context) {
	pipelineVersionId := c.Param("id")
	if pipelineVersionId == "" {
		hls := strings.ReplaceAll(httpex.HTMLMsgShow, "{{msg}}", "param err")
		c.Data(404, "text/html", []byte(hls))
		return
	}
	bd := &pipeline.TBuild{}
	ok, _ := comm.DBMain.GetDB().Where("pipeline_version_id=?", pipelineVersionId).Get(bd)
	if !ok {
		hls := strings.ReplaceAll(httpex.HTMLMsgShow, "{{msg}}", "日志未找到")
		c.Data(404, "text/html", []byte(hls))
		return
	}
	bdpth := filepath.Join(comm.WorkPath, comm.Build, bd.Id, "build.log")
	_, err := os.Stat(bdpth)
	if err != nil {
		conts := "构建异常，日志文件未找到"
		if common.BuildStatusEnded(bd.Status) {
			conts = "未执行完成，无法下载"
		}
		hls := strings.ReplaceAll(httpex.HTMLMsgShow, "{{msg}}", conts)
		c.Data(404, "text/html", []byte(hls))
		return
	}
	bts, err := ioutil.ReadFile(bdpth)
	if err != nil {
		hls := strings.ReplaceAll(httpex.HTMLMsgShow, "{{msg}}", "日志文件未找到")
		c.Data(404, "text/html", []byte(hls))
		return
	}
	c.Header("Content-Disposition", `attachment;filename="build.log"`)
	c.Data(200, "text/plain", bts)
}
func (PipelineController) downJobLog(c *gin.Context) {
	jobId := c.Param("id")
	if jobId == "" {
		hls := strings.ReplaceAll(httpex.HTMLMsgShow, "{{msg}}", "param err")
		c.Data(404, "text/html", []byte(hls))
		return
	}
	job := &models.TJob{}
	ok, _ := comm.DBMain.GetDB().Where("id=?", jobId).Get(job)
	if !ok {
		hls := strings.ReplaceAll(httpex.HTMLMsgShow, "{{msg}}", "日志未找到")
		c.Data(404, "text/html", []byte(hls))
		return
	}
	dir := filepath.Join(comm.WorkPath, comm.Build, job.BuildId, comm.Jobs)
	logpth := filepath.Join(dir, fmt.Sprintf("%v.log", job.Id))
	fl, err := os.Open(logpth)
	if err != nil {
		hls := strings.ReplaceAll(httpex.HTMLMsgShow, "{{msg}}", "日志文件未找到")
		c.Data(404, "text/html", []byte(hls))
		return
	}
	defer fl.Close()
	c.Header("Content-Type", "text/plain")
	c.Header("Content-Disposition", fmt.Sprintf(`attachment;filename="job-%s.log"`, job.Name))
	c.Status(200)
	bts := make([]byte, 1024*5)
	linebuf := &bytes.Buffer{}
	for !ioex.CheckContext(c) { // 循环解析日志文件,每一行一个json
		rn, err := fl.Read(bts)
		if rn > 0 {
			for i := 0; i < rn; i++ {
				b := bts[i]
				if linebuf == nil && b == '{' {
					linebuf.Reset()
				}
				if linebuf != nil {
					if b == '\n' {
						e := &hbtpBean.CmdLogLineJson{}
						err := json.Unmarshal(linebuf.Bytes(), e)
						linebuf.Reset()
						if err == nil {
							/*if e.Type == hbtpBean.TypeCmdLogLineSys {
								continue
							}*/
							//ls = append(ls, e)
							c.Writer.WriteString(fmt.Sprintf("%s ", e.Times.Format(common.TimeFmt)))
							if e.Type == hbtpBean.TypeCmdLogLineSysrun {
								c.Writer.WriteString("+ ")
							} /*else if e.Type==hbtpBean.TypeCmdLogLineSysend{
								ende=v
							}*/
							c.Writer.WriteString(e.Content)
							c.Writer.WriteString("\n")
						}
					} else {
						linebuf.WriteByte(b)
					}
				}
			}
		}
		if err != nil {
			break
		}
	}
}

func (PipelineController) stop(c *gin.Context, m *utils.Map) {
	pipelineVersionId := m.GetString("pipelineVersionId")
	if pipelineVersionId == "" {
		core.NewUIRes(common.UICodeErrParam).
			ResJson(c)
		return
	}
	bd := &pipeline.TBuild{}
	db := comm.DBMain.GetDB()
	ok, _ := db.Where("pipeline_version_id=?", pipelineVersionId).Get(bd)
	if !ok {
		core.NewUIRes(common.UICodeErrNotFound).
			ResJson(c)
		return
	}
	tvp := &pipeline.TPipelineVersion{}
	get, err := db.Where("id = ?", pipelineVersionId).Where("deleted != 1").Get(tvp)
	if err != nil {
		core.NewUIRes(common.UICodeErrs).
			SetMsgObjs(err).
			ResJson(c)
		return
	}
	if !get {
		core.NewUIRes(common.UICodeErrNotFound).
			SetMsgObjs(err).
			ResJson(c)
		return
	}
	te := &models.ShowStopRepo{}
	get, err = db.Where("id = ?", tvp.RepoId).Where("deleted != 1").Get(te)
	if err != nil {
		core.NewUIRes(common.UICodeErrs).
			SetMsgObjs(err).
			ResJson(c)
		return
	}
	if !get {
		core.NewUIRes(common.UICodeErrNotFound).
			SetMsgObjs(err).
			ResJson(c)
		return
	}
	ok = engine.Mgr().GetBuildEgn().StopTask(bd.Id)
	te.Status = ok
	core.NewUIResOk(te).ResJson(c)
}
func (PipelineController) plugins(c *gin.Context, m *utils.Map) {
	core.NewUIResOk(comm2.PlugNms()).ResJson(c)
}

func (PipelineController) savePipeline(c *gin.Context, m *utils.Map) {
	fileName := m.GetString("fileName")
	sha := m.GetString("sha")
	content := m.GetString("content")
	repoId := m.GetString("repoId")
	branch := m.GetString("branch")
	if fileName == "" || content == "" || repoId == "" {
		core.NewUIRes(common.UICodeErrParam).
			ResJson(c)
		return
	}
	user := service.GetMidLgUser(c)
	db := comm.DBMain.GetDB()
	tu := &pipeline.TUserToken{}
	get, err := db.Where("uid = ?", user.Id).Get(tu)
	if err != nil {
		core.NewUIRes(common.UICodeErrs).
			SetMsgObjs(err).
			ResJson(c)
		return
	}
	if !get || tu.AccessToken == "" {
		core.NewUIRes(common.UICodeErrs).
			SetMsgObjs("该用户未授权").
			ResJson(c)
		return
	}
	repo := &pipeline.TRepo{}
	get, err = db.Where("id = ?", repoId).Get(repo)
	if err != nil {
		core.NewUIRes(common.UICodeErrs).
			SetMsgObjs(err).
			ResJson(c)
		return
	}
	if !get {
		core.NewUIRes(common.UICodeErrNotFound).
			SetMsgObjs("仓库不存在").
			ResJson(c)
		return
	}

	yl := &models.YML{}
	pie := &models.Pipeline{}
	err = yaml.Unmarshal([]byte(content), pie)
	if err != nil {
		core.NewUIRes(common.UICodeErrs).
			SetMsgObjs(err).
			ResJson(c)
		return
	}
	yl.Pie = pie
	yl.Check()
	marshal, err := yl.Marshal()
	if err != nil {
		core.NewUIRes(common.UICodeErrs).
			SetMsgObjs(err).
			ResJson(c)
		return
	}
	dagT, err := ymldag.GetDAG()
	if err != nil {
		core.NewUIRes(common.UICodeErrs).
			SetMsgObjs(err).
			ResJson(c)
		return
	}
	err = dag.CheckJsonWithDag(marshal, []byte(dagT))
	if err != nil {
		core.NewUIRes(common.UICodeErrs).
			SetMsgObjs(err).
			ResJson(c)
		return
	}
	appinfo, err := service.GetsParamOAuthKey()
	if err != nil {
		core.NewUIRes(common.UICodeErrs).
			SetMsgObjs(err).
			ResJson(c)
		return
	}
	k, info := appinfo.DefAppInfo()
	cl, err := comm.GetThirdApi(k, info.SourceHost)
	if err != nil {
		core.NewUIRes(common.UICodeErrs).
			SetMsgObjs(err).
			ResJson(c)
		return
	}

	if sha == "" {
		_, err = cl.Repositories.PushFile(tu.AccessToken, repo.Owner, repo.Name, fmt.Sprintf("%s/%s.yaml", comm.YamlDir, fileName), content, branch)
		if err != nil {
			core.NewUIRes(common.UICodeErrs).
				SetMsgObjs(err).
				ResJson(c)
			return
		}
	} else {
		_, err = cl.Repositories.UpdateFile(tu.AccessToken, repo.Owner, repo.Name, fmt.Sprintf("%s/%s.yaml", comm.YamlDir, fileName), sha, content, branch)
		if err != nil {
			core.NewUIRes(common.UICodeErrs).
				SetMsgObjs(err).
				ResJson(c)
			return
		}
	}
	core.NewUIResOk().ResJson(c)
}

func (PipelineController) deletedVersion(c *gin.Context, m *utils.Map) {
	pipelineVersionId := m.GetString("pipelineVersionId")
	if pipelineVersionId == "" {
		core.NewUIRes(common.UICodeErrParam).
			ResJson(c)
		return
	}
	tvp := &pipeline.TPipelineVersion{}
	db := comm.DBMain.GetDB()
	get, err := db.Where("id = ?", pipelineVersionId).Where("deleted != 1").Get(tvp)
	if err != nil {
		core.NewUIRes(common.UICodeErrs).
			SetMsgObjs(err).
			ResJson(c)
		return
	}
	if !get {
		core.NewUIRes(common.UICodeErrNotFound).
			SetMsgObjs(err).
			ResJson(c)
		return
	}
	te := &models.TRepo{}
	get, err = db.Where("id = ?", tvp.RepoId).Where("deleted != 1").Get(te)
	if err != nil {
		core.NewUIRes(common.UICodeErrs).
			SetMsgObjs(err).
			ResJson(c)
		return
	}
	if !get {
		core.NewUIRes(common.UICodeErrNotFound).
			SetMsgObjs(err).
			ResJson(c)
		return
	}
	tvp.Deleted = 1
	_, err = db.Where("id = ?", pipelineVersionId).Cols("deleted").Update(tvp)
	if err != nil {
		core.NewUIRes(common.UICodeErrs).
			SetMsgObjs(err).
			ResJson(c)
		return
	}
	core.NewUIResOk(te).ResJson(c)
}

func (cs *PipelineController) artifacts(c *gin.Context, m *utils.Map) {
	jobId := m.GetString("jobId")
	if jobId == "" {
		core.NewUIRes(common.UICodeErrParam).
			ResJson(c)
		return
	}
	tJob := &pipeline.TJob{}
	ok, _ := comm.DBMain.GetDB().Where("id=?", jobId).Get(tJob)
	if !ok {
		core.NewUIRes(common.UICodeErrNotFound).
			ResJson(c)
		return
	}
	var ls []*models.TArtifactArchive
	err := comm.DBMain.GetDB().Where("job_id=?", tJob.Id).Find(&ls)
	if err != nil {
		core.NewUIRes(common.UICodeErrs).
			SetMsgObjs(err).
			ResJson(c)
		return
	}

	dir := filepath.Join(comm.WorkPath, comm.Build, tJob.BuildId, comm.Jobs)
	rets := make([]*models.TArtifactArchive, 0)
	for _, v := range ls {
		if err := cs.flinfo(dir, v); err != nil {
			continue
		}
		rets = append(rets, v)
	}

	core.NewUIResOk(ls).ResJson(c)
}

func (PipelineController) flinfo(dir string, v *models.TArtifactArchive) error {
	flpth := filepath.Join(dir, fmt.Sprintf("%s.art", v.ArtId))
	stat, err := os.Stat(flpth)
	if err != nil {
		return err
	}
	v.FileSize = stat.Size()
	v.FileName = v.Name
	rdr, err := zip.OpenReader(flpth)
	if err != nil {
		return err
	}
	defer rdr.Close()
	if len(rdr.File) > 0 { // 可能有windows产生的zip,需要替换\为/
		flv := rdr.File[0]
		nms := flv.Name
		strings.ReplaceAll(nms, "\\", "/")
		if strings.HasPrefix(nms, "/") {
			nms = strings.Split(nms[1:], "/")[0]
		}
		mms := strings.Split(nms, "/")
		if len(mms) > 1 {
			v.FileExt = filepath.Ext(mms[0])
			v.FileName += v.FileExt
		}
	}
	/*for _, f := range rdr.File {
		v.FileName=f.Name
		break
	}*/
	return nil
}
func (cs *PipelineController) artDel(c *gin.Context, m *utils.Map) {
	ids, ok := m.Get("ids")
	if !ok {
		core.NewUIRes(common.UICodeErrParam).
			ResJson(c)
		return
	}
	n := 0
	switch ids.(type) {
	case []string:
		for _, v := range ids.([]string) {
			if err := cs.artdels(v); err != nil {
				continue
			}
			n++
		}
	case []interface{}:
		for _, v := range ids.([]interface{}) {
			if err := cs.artdels(fmt.Sprintf("%v", v)); err != nil {
				continue
			}
			n++
		}
	default:
		core.NewUIRes(common.UICodeErrParam).
			ResJson(c)
		return
	}
	core.NewUIResOk(n).ResJson(c)
}
func (PipelineController) artdels(artifactId string) error {
	if artifactId == "" {
		return fmt.Errorf("artifactId err")
	}
	tav := &pipeline.TArtifactArchive{}
	ok, _ := comm.DBMain.GetDB().Where("id=?", artifactId).Get(tav)
	if !ok {
		ok, _ = comm.DBMain.GetDB().Where("xid=?", artifactId).Get(tav)
	}
	if !ok {
		return fmt.Errorf("not Found")
	}
	tJob := &pipeline.TJob{}
	ok, _ = comm.DBMain.GetDB().Where("id=?", tav.JobId).Get(tJob)
	if !ok {
		return fmt.Errorf("not Found")
	}

	_, err := comm.DBMain.GetDB().Where("id=?", tav.Id).Delete(tav)
	if err != nil {
		return err
	}

	dir := filepath.Join(comm.WorkPath, comm.Build, tJob.BuildId, comm.Jobs)
	flpth := filepath.Join(dir, fmt.Sprintf("%s.art", tav.ArtId))
	os.RemoveAll(flpth)
	return nil
}

type tmpfls struct {
	pth string
	nms string
}

func (PipelineController) artDowns(c *gin.Context) {
	ids := c.QueryArray("id")
	if len(ids) <= 0 {
		hls := strings.ReplaceAll(httpex.HTMLMsgShow, "{{msg}}", "param err")
		c.Data(404, "text/html", []byte(hls))
		return
	}
	var ls []*tmpfls
	for _, v := range ids { // 查出所有制品文件,插入slice
		if v == "" {
			continue
		}
		tav := &pipeline.TArtifactArchive{}
		ok, _ := comm.DBMain.GetDB().Where("id=?", v).Get(tav)
		if !ok {
			ok, _ = comm.DBMain.GetDB().Where("xid=?", v).Get(tav)
		}
		if !ok {
			continue
		}
		tJob := &pipeline.TJob{}
		ok, _ = comm.DBMain.GetDB().Where("id=?", tav.JobId).Get(tJob)
		if !ok {
			continue
		}
		dir := filepath.Join(comm.WorkPath, comm.Build, tJob.BuildId, comm.Jobs)
		flpth := filepath.Join(dir, fmt.Sprintf("%s.art", tav.ArtId))
		_, err := os.Stat(flpth)
		if err != nil {
			continue
		}
		ls = append(ls, &tmpfls{
			pth: flpth,
			nms: tav.Name,
		})
	}
	if len(ls) <= 0 {
		hls := strings.ReplaceAll(httpex.HTMLMsgShow, "{{msg}}", "参数错误")
		c.Data(404, "text/html", []byte(hls))
		return
	}

	nm := "all"
	zipth := fmt.Sprintf("/tmp/%s.zip", utils.NewXid())
	if len(ls) == 1 {
		nm = ls[0].nms
		zipth = ls[0].pth
	} else {
		zFile, err := os.Create(zipth)
		if err != nil {
			hls := strings.ReplaceAll(httpex.HTMLMsgShow, "{{msg}}", "文件错误:"+err.Error())
			c.Data(404, "text/html", []byte(hls))
			return
		}
		defer os.RemoveAll(zipth)
		defer zFile.Close()

		w := zip.NewWriter(zFile)
		defer w.Close()

		for _, v := range ls { // 压缩文件,利于下载
			p := fmt.Sprintf("/%s.zip", v.nms)
			f, err := w.Create(p)
			if err != nil {
				continue
			}
			file, err := os.Open(v.pth)
			if err != nil {
				continue
			}
			_, err = io.Copy(f, file)
			file.Close()
			if err != nil {
				continue
			}
		}
		w.Close()
		zFile.Close()
	}

	stat, err := os.Stat(zipth)
	if err != nil {
		hls := strings.ReplaceAll(httpex.HTMLMsgShow, "{{msg}}", "文件损坏")
		c.Data(404, "text/html", []byte(hls))
		return
	}
	fl, err := os.Open(zipth)
	if err != nil {
		hls := strings.ReplaceAll(httpex.HTMLMsgShow, "{{msg}}", "文件损坏")
		c.Data(404, "text/html", []byte(hls))
		return
	}
	defer fl.Close()
	c.Header("Connection", "Keep-Alive")
	c.Header("Content-Type", "application/octet-stream")
	c.Header("Cache-Control", "max-age=360000000")
	c.Header("Content-Length", fmt.Sprintf("%d", stat.Size()))
	c.Header("Content-Disposition", fmt.Sprintf(`attachment;filename="%s"`, url.QueryEscape(nm+".zip")))
	c.Status(200)
	bts := make([]byte, 1024)
	for !ioex.CheckContext(c) { // 发送文件流
		n, err := fl.Read(bts)
		if n > 0 {
			_, errw := c.Writer.Write(bts[:n])
			if errw != nil {
				break
			}
		}
		if err != nil {
			break
		}
	}
}
